﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography;
using System.Management;
using System.Globalization;
using System.Text.RegularExpressions;

namespace GenerateKey
{
    public class GenKey
    {
        // define const for key
        private const int KEY_LENGTH = 29;
        private const int MAX_KEY_DAYS = 3650;// 10 year        

        // define const for validate
        public const int ERROR_KEY_EMPTY = 0;
        public const int ERROR_KEY_LENGTH = 1;
        public const int ERROR_KEY_FORMAT = 2;
        public const int ERROR_KEY_MAINBOARD_ID = 3;
        public const int ERROR_KEY_EXPIRED_DATE = 4;
        public const int ERROR_KEY_MAX_EXPIRED_DATE = 5;
        public const int VALID_KEY = 6;

        public static int Validate(string serial, string key)
        {
            if (string.IsNullOrEmpty(key) || key.Length == 0)
                return ERROR_KEY_EMPTY;
            else if (key.Length != GenKey.KEY_LENGTH)
                return ERROR_KEY_LENGTH;
            else
            {
                if (key.Count(c => c == '-') != 5 ||
                    key[4] != '-' ||
                    key[9] != '-' ||
                    key[14] != '-' ||
                    key[19] != '-' ||
                    key[24] != '-'
                    )
                    return ERROR_KEY_FORMAT;

                if (!GetKey_MainboardId(key).Equals(Value(serial)))
                    return ERROR_KEY_MAINBOARD_ID;

                else
                {
                    int days = GenKey.GetDaysLeft(key);

                    // key expired
                    if (days < 0)
                        return ERROR_KEY_EXPIRED_DATE;

                    // custommer cheat license because key for 2 year
                    else if (days > MAX_KEY_DAYS)
                        return ERROR_KEY_MAX_EXPIRED_DATE;

                    return VALID_KEY;
                }
            }
        }


        private static string fingerPrint = string.Empty;
        public static string Value()
        {
            if (string.IsNullOrEmpty(fingerPrint))
            {
                fingerPrint = GetHash(//"CPU >> " + cpuId() + "\nBIOS >> " + biosId() + "\nBASE >> " + baseId()
                    //+"\nDISK >> "+ diskId() + "\nVIDEO >> " + videoId() +"\nMAC >> "+ macId() + expriedDate()
                          GetMotherBoardID()
                           );
            }
            return fingerPrint;
        }

        public static string Value(string productId)
        {
            //var mainBoardId = GetMainBoardID(serial);
            fingerPrint = GetHash(//"CPU >> " + cpuId() + "\nBIOS >> " + biosId() + "\nBASE >> " + baseId()
                //+"\nDISK >> "+ diskId() + "\nVIDEO >> " + videoId() +"\nMAC >> "+ macId() + expriedDate()
                      productId
                       );

            return fingerPrint;
        }
        private static string GetHash(string s)
        {
            MD5 sec = new MD5CryptoServiceProvider();
            ASCIIEncoding enc = new ASCIIEncoding();
            byte[] bt = enc.GetBytes(s);
            return GetHexString(sec.ComputeHash(bt));
        }
        private static string GetHexString(byte[] bt)
        {
            string s = string.Empty;
            for (int i = 0; i < bt.Length / 2; i++)
            {
                byte b = bt[i];
                int n, n1, n2;
                n = (int)b;
                n1 = n & 15;
                n2 = (n >> 4) & 15;
                if (n2 > 9)
                    s += ((char)(n2 - 10 + (int)'A')).ToString();
                else
                    s += n2.ToString();
                if (n1 > 9)
                    s += ((char)(n1 - 10 + (int)'A')).ToString();
                else
                    s += n1.ToString();
                if ((i + 1) != bt.Length / 2 && (i + 1) % 2 == 0) s += "-";
            }
            return s;
        }
        #region Original Device ID Getting Code
        //Return a hardware identifier
        private static string identifier(string wmiClass, string wmiProperty, string wmiMustBeTrue)
        {
            string result = "";
            System.Management.ManagementClass mc = new System.Management.ManagementClass(wmiClass);
            System.Management.ManagementObjectCollection moc = mc.GetInstances();
            foreach (System.Management.ManagementObject mo in moc)
            {
                if (mo[wmiMustBeTrue].ToString() == "True")
                {
                    //Only get the first one
                    if (result == "")
                    {
                        try
                        {
                            result = mo[wmiProperty].ToString();
                            break;
                        }
                        catch
                        {
                        }
                    }
                }
            }
            return result;
        }
        //Return a hardware identifier
        private static string identifier(string wmiClass, string wmiProperty)
        {
            string result = "";
            System.Management.ManagementClass mc = new System.Management.ManagementClass(wmiClass);
            System.Management.ManagementObjectCollection moc = mc.GetInstances();
            foreach (System.Management.ManagementObject mo in moc)
            {
                //Only get the first one
                if (result == "")
                {
                    try
                    {
                        result = mo[wmiProperty].ToString();
                        break;
                    }
                    catch
                    {
                    }
                }
            }
            return result;
        }
        private static string cpuId()
        {
            //Uses first CPU identifier available in order of preference
            //Don't get all identifiers, as very time consuming
            string retVal = identifier("Win32_Processor", "UniqueId");
            if (retVal == "") //If no UniqueID, use ProcessorID
            {
                retVal = identifier("Win32_Processor", "ProcessorId");
                if (retVal == "") //If no ProcessorId, use Name
                {
                    retVal = identifier("Win32_Processor", "Name");
                    if (retVal == "") //If no Name, use Manufacturer
                    {
                        retVal = identifier("Win32_Processor", "Manufacturer");
                    }
                    //Add clock speed for extra security
                    retVal += identifier("Win32_Processor", "MaxClockSpeed");
                }
            }
            return retVal;
        }
        //BIOS Identifier
        private static string biosId()
        {
            return identifier("Win32_BIOS", "Manufacturer")
            + identifier("Win32_BIOS", "SMBIOSBIOSVersion")
            + identifier("Win32_BIOS", "IdentificationCode")
            + identifier("Win32_BIOS", "SerialNumber")
            + identifier("Win32_BIOS", "ReleaseDate")
            + identifier("Win32_BIOS", "Version");
        }
        //Main physical hard drive ID
        private static string diskId()
        {
            return identifier("Win32_DiskDrive", "Model")
            + identifier("Win32_DiskDrive", "Manufacturer")
            + identifier("Win32_DiskDrive", "Signature")
            + identifier("Win32_DiskDrive", "TotalHeads");
        }
        //Motherboard ID
        private static string baseId()
        {
            return identifier("Win32_BaseBoard", "Model")
            + identifier("Win32_BaseBoard", "Manufacturer")
            + identifier("Win32_BaseBoard", "Name")
            + identifier("Win32_BaseBoard", "SerialNumber");
        }
        //Primary video controller ID
        private static string videoId()
        {
            return identifier("Win32_VideoController", "DriverVersion")
            + identifier("Win32_VideoController", "Name");
        }
        //First enabled network card ID
        private static string macId()
        {
            return identifier("Win32_NetworkAdapterConfiguration", "MACAddress", "IPEnabled");
        }
        #endregion

        public static string GetMotherBoardID()
        {
            string mbInfo = String.Empty;

            //Get motherboard's serial number 
            ManagementObjectSearcher mbs = new ManagementObjectSearcher("Select * From Win32_BaseBoard");
            foreach (ManagementObject mo in mbs.Get())
            {
                mbInfo += mo["SerialNumber"].ToString();
            }
            return mbInfo;//108FEA6MBQT02730   CNF0293WYX  CNF0293WYX
        }

        /// <summary>
        /// Get key of mainboadId from key
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public static string GetKey_MainboardId(string key)
        {
            // get 19 first character from key
            return key.Substring(0, 19);
        }

        private static string GetKey_ExpiredDate(string serial)
        {
            //int nIndexStart = 20;
            return serial.Substring(20, 4) + serial.Substring(25, 4);
        }

        /// <summary>
        /// Get expired date from a key
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public static DateTime GetExpiredDate(string key)
        {
            // get key for expired date from key
            string keyExpired = GetKey_ExpiredDate(key);

            int month = DecodeMonth(keyExpired.Substring(0, 2));
            int day = DecodeDay(keyExpired.Substring(2, 2));
            int year = DecodeYear(keyExpired.Substring(4, 4));



            try
            {
                return new DateTime(year, month, day);
            }
            catch
            {
                return DateTime.MinValue;
            }
        }

        /// <summary>
        /// Get days left of a key
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public static int GetDaysLeft(string key)
        {
            // get expired date from the serial
            var expiredDate = GenKey.GetExpiredDate(key);

            // get system date now
            var nowDate = DateTime.Now.Date;

            // compare expired date vs date now
            TimeSpan difference = expiredDate - nowDate;
            int days = (int)Math.Ceiling(difference.TotalDays);

            return days;
        }


        public static bool IsExpired(string key)
        {
            return GetDaysLeft(key) < 0 ? true : false;
        }

        #region encode key

        /// <summary>
        /// số chẵn thì return về nó số lẻ thì encode
        /// 1 - A
        /// 3 - I
        /// 5 - M
        /// 7 - R
        /// 9 - X
        /// </summary>
        /// <param name="number"></param>
        /// <returns></returns>
        public static string EncodeNumber(int number)
        {
            if (number % 2 == 0)
                return number.ToString();
            else
            {
                switch (number)
                {
                    case 1:
                        return "A";
                    case 3:
                        return "I";
                    case 5:
                        return "M";
                    case 7:
                        return "R";
                    default:
                        //case 9:
                        return "X";
                }
            }
        }

        public static int DecodeNumber(char charNumber)
        {
            switch (charNumber)
            {
                case 'A':
                    return 1;
                case 'I':
                    return 3;
                case 'M':
                    return 5;
                case 'R':
                    return 7;
                case 'X':
                    return 9;
                case '1':
                case '3':
                case '5':
                case '7':
                case '9':
                    return -9999;
            }

            int number = -9999;

            Int32.TryParse(charNumber.ToString(), out number);
            return number;
        }

        public static string EncodeMonth(int month)
        {
            if (month < 10)
                return "0" + EncodeNumber(month);
            else
                return EncodeNumber(month / 10) + EncodeNumber(month % 10);
        }

        public static int DecodeMonth(string keyMonth)
        {
            return DecodeNumber(keyMonth[0]) * 10 + DecodeNumber(keyMonth[1]);
        }


        public static string EncodeDay(int day)
        {
            if (day < 10)
                return "0" + EncodeNumber(day);
            else
                return EncodeNumber(day / 10) + EncodeNumber(day % 10);
        }

        public static int DecodeDay(string keyDay)
        {
            return DecodeNumber(keyDay[0]) * 10 + DecodeNumber(keyDay[1]);
        }


        public static string EncodeYear(int year)
        {
            return EncodeNumber(year / 1000)
                + EncodeNumber((year / 100) % 10)
                + EncodeNumber((year / 10) % 10)
                + EncodeNumber(year % 10);
        }

        public static int DecodeYear(string keyYear)
        {
            return DecodeNumber(keyYear[0]) * 1000
                + DecodeNumber(keyYear[1]) * 100
                + DecodeNumber(keyYear[2]) * 10
                + DecodeNumber(keyYear[3]);
        }


        /// <summary>
        /// Generate key for expired date
        /// </summary>
        /// <param name="expiredDate"></param>
        /// <returns>Hex value of expired date</returns>
        public static string GenKey_ExpiredDate(DateTime expiredDate)
        {
            return EncodeMonth(expiredDate.Month) + EncodeDay(expiredDate.Day) + EncodeYear(expiredDate.Year);
        }

        /// <summary>
        /// Generate key from serial(mainboardID) and expired date
        /// </summary>
        /// <param name="serial"></param>
        /// <param name="expiredDate"></param>
        /// <returns></returns>
        public static string GetKey(string serial, DateTime expiredDate)
        {
            // get mainboardID key from serial(10 first character from mainboardID)
            var keyMainBoardId = GenKey.Value(serial);
            // get expired date key
            var keyExpiredDate = GenKey_ExpiredDate(expiredDate);

            // remove '-' in serial key
            keyMainBoardId = keyMainBoardId.Replace("-", string.Empty);

            // key
            var tempKey = keyMainBoardId + keyExpiredDate;

            // format the key: XXXX-XXXX-XXXX-XXXX-XXXX-XXXX
            string key = string.Empty;
            for (int i = 0; i < tempKey.Length; i++)
            {
                key += tempKey[i];

                // separate key with '-'
                if ((i + 1) % 4 == 0 && (i + 1) != tempKey.Length)
                    key += "-";
            }

            return key;
        }
        #endregion

        public static string GetProductId()
        {
            string text = GetMotherBoardID().ToUpper();
            text = Regex.Replace(text, @"[^0-9a-zA-Z]+", "");

            return text.Length > 10 ? text.Substring(0, 10) : text;
        }

        /// <summary>
        /// Generate new key for renew(1 year)
        /// </summary>
        /// <param name="serial"></param>
        /// <param name="oldExpiredDate"></param>
        /// <returns></returns>
        public static string Renew(string serial, DateTime oldExpiredDate)
        {
            return GetKey(serial, oldExpiredDate.AddYears(1));
        }
    }
}
